package mesosphere.mesos.scale

import java.net.URL

import play.api.libs.json._

import scala.io.Source

/**
  * Base trait for all metrics
  */
sealed trait Metric {
  def name: String
  def mean: Double
}

case class Gauge(name: String, value: Double) extends Metric {
  override def mean: Double = value
}

case class Counter(name: String, count: Int) extends Metric {
  override def mean: Double = count.toDouble
}

case class Histogram(
    name: String,
    count: Int,
    max: Double,
    mean: Double,
    min: Double,
    p50: Double,
    p75: Double,
    p95: Double,
    p98: Double,
    p99: Double,
    p999: Double,
    stddev: Double
) extends Metric

case class Meter(name: String, count: Int, m15_rate: Double, m1_rate: Double, m5_rate: Double, mean_rate: Double, units: String)
    extends Metric {
  override def mean: Double = mean_rate
}

case class Timer(
    name: String,
    count: Int,
    max: Double,
    mean: Double,
    min: Double,
    p50: Double,
    p75: Double,
    p95: Double,
    p98: Double,
    p99: Double,
    p999: Double,
    stddev: Double,
    m15_rate: Double,
    m1_rate: Double,
    m5_rate: Double,
    mean_rate: Double,
    duration_units: String,
    rate_units: String
) extends Metric

case class MetricsSample(
    version: String,
    gauges: Seq[Gauge],
    counters: Seq[Counter],
    histograms: Seq[Histogram],
    meters: Seq[Meter],
    timers: Seq[Timer]
) {

  def all: Map[String, Seq[Metric]] =
    Map(
      "gauges" -> gauges,
      "counters" -> counters,
      "gauges" -> gauges,
      "histograms" -> histograms,
      "meters" -> meters,
      "timers" -> timers
    )
}

object MetricsFormat {

  /**
    * Special Reads function, that transform reads a given json object
    *  { "a" : {}, "b" : {} } into an array of form
    *  [ { "name": "a", ... }, { "name": "b", ...} ]
    *  The given reads function is used to read the transformed json into an object.
    */
  def objectRead[T](t: Reads[T]): Reads[Seq[T]] =
    new Reads[Seq[T]] {
      override def reads(js: JsValue): JsResult[Seq[T]] = {
        JsSuccess(
          js.as[JsObject]
            .fields
            .iterator
            .map {
              case (name, value) =>
                val obj = JsObject(value.as[JsObject].fields :+ ("name" -> JsString(name)))
                t.reads(obj).get
            }
            .toSeq
        )
      }
    }

  // Json Reads objects for metric structure
  implicit val gaugeReads = objectRead(Json.reads[Gauge])
  implicit val countReads = objectRead(Json.reads[Counter])
  implicit val histogramReads = objectRead(Json.reads[Histogram])
  implicit val meterReads = objectRead(Json.reads[Meter])
  implicit val timerReads = objectRead(Json.reads[Timer])
  implicit val sampleReads: Reads[MetricsSample] = Json.reads[MetricsSample]

  def readMetrics(url: URL): Seq[MetricsSample] = {
    val jsonString = Source.fromURL(url, "UTF-8").mkString
    Json.parse(jsonString).as[Seq[MetricsSample]]
  }

  def readMetric(url: URL): MetricsSample = {
    val jsonString = Source.fromURL(url, "UTF-8").mkString
    Json.parse(jsonString).as[MetricsSample]
  }
}
